/*
 * Piso.cpp
 *
 */

#include "Piso.h"

/**
 * Contructor del piso.
 * Recibe los parametros de ejecucion del Piso, un puntero del pipe
 * para pasarle llamadas al controlador y dos punteros de los pipes
 * para pasarle personas al ascensor dependiendo de si las personas
 * baje o suban de piso.
 *
 */
Piso::Piso( int nroPiso, int cantidadPisos, Pipe* pipeLlamadas, Pipe* pipePersonasSuben, Pipe* pipePersonasBajan  )
	: nroPiso ( nroPiso) ,
	  cantidadPisos ( cantidadPisos ),
	  pipeLlamadas ( pipeLlamadas ) ,
	  realizarLlamadas ( true ),
	  pipePersonasSuben ( pipePersonasSuben ),
	  pipePersonasBajan ( pipePersonasBajan ),
	  totalPersonas ( 0 ) {

}

/**
 * Metodo que inicializa los mecanismos de concurrencia que usa el Piso.
 * Registra los handlers de seniales.
 */
int Piso::inicializar(){
	srand ( getMilisegundos() );
	SignalHandler :: getInstance()->registrarHandler( SIGUSR2, this );
	SignalHandler :: getInstance()->registrarHandler( SIGINT, this );
	SignalHandler :: getInstance()->registrarHandler( SIGTERM, this );
	SignalHandler :: getInstance()->registrarHandler( SIGABRT, this );

	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": creado.");
	return 0;
}

/**
 * Destructor del piso.
 * Elimina los handlers de seniales, cierra el pipe de llamadas y los
 * dos pipes para pasarle personas al ascensor.
 */
Piso::~Piso() {
	SignalHandler::getInstance()->removerHandler ( SIGUSR2 );
	SignalHandler::getInstance()->removerHandler ( SIGINT );
	SignalHandler::getInstance()->removerHandler ( SIGTERM );
	SignalHandler::getInstance()->removerHandler ( SIGABRT );
	SignalHandler::destruir();

	this->pipeLlamadas->cerrar();
	this->pipePersonasSuben->cerrar();
	this->pipePersonasBajan->cerrar();

	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": destruido.");
}

/**
 * Handler de seniales:
 * SIGUSR2: Senial que envia el terminador para que el piso deje de
 * 		generar personas.
 * SIGINT: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGTERM: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGABRT: Senial que envia el terminador en caso de que haya errores.
 * 		Se finaliza la ejecucion inmediatamente liberando los recursos.
 */
int Piso::handleSignal ( int signum ) {
	if ( signum == SIGUSR2 ){
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGUSR2, no genero mas llamadas.");
		this->realizarLlamadas=false;

	} else if ( signum == SIGINT ){
		// Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGINT, no hago nada.");
		// El terminador (padre) se encarga de agarrar la senial.

	} else if ( signum == SIGTERM ){
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGTERM, no hago nada.");
		// El terminador (padre) se encarga de agarrar la senial.

	} else if ( signum == SIGABRT ){
		Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": me llego SIGABRT, finalizo ante errores.");
		this->~Piso();
		exit( -1 );

	}
	return 0;
}

/**
 * Metodo que se ejecuta de forma continua simulando el arribo aleatorio
 * de personas al piso.
 */
void Piso::generarLlamadas(){
	usleep ( this->nroPiso * 100000 + calcularRandom ( PERIODO_MINIMO_LLAMADAS, PERIODO_MAXIMO_LLAMADAS) * 1000000 );
	while ( this->realizarLlamadas ){
		Persona persona = this->construirPersona();
		this->realizarLlamada( persona );
		sleep ( calcularRandom ( PERIODO_MINIMO_LLAMADAS, PERIODO_MAXIMO_LLAMADAS ) );
	}
	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": total de personas que arribaron: " + intToString( this->totalPersonas ) );
}

/**
 * Metodo que devuelve una persona que arriba al piso.
 */
Persona Piso::construirPersona(){
	int pisoDestino = this->calcularPisoDestino();
	Persona persona( pisoDestino );

	return persona;
}

/**
 * Metodo que escribe la persona en el pipe correspondiente,
 * y escribe la llamada en el pipe de llamadas para que el controlador
 * la lea.
 *
 */
void Piso::realizarLlamada( Persona persona ){
	Llamada llamada;
	llamada.setPiso( this->nroPiso );
	if ( persona.getPisoDestino() > this->nroPiso ) {
		this->pipePersonasSuben->escribir( &persona, sizeof( Persona ) );
		llamada.setTipoLlamada( LLAMADA_SUBIR );
	} else {
		this->pipePersonasBajan->escribir( &persona, sizeof( Persona ) );
		llamada.setTipoLlamada( LLAMADA_BAJAR );
	}
	this->totalPersonas++;

	Log::getInstance()->imprimir("Piso " + intToString( this->nroPiso ) + ": llega una persona que va al piso " + intToString( persona.getPisoDestino() ) + ".");
	Log::getInstance()->loguear("Piso " + intToString( this->nroPiso ) + ": meto una llamada al ascensor en el pipe.");
	this->pipeLlamadas->escribir( &llamada, sizeof( Llamada ) );
}

/**
 * Calcula un numero random que no coincide con el numero de piso actual.
 */
int Piso::calcularPisoDestino(){
	int pisoDestino;
	do {
		pisoDestino = calcularRandom ( this->cantidadPisos );
	} while ( pisoDestino == this->nroPiso );
	return pisoDestino;
}


